package sf.ext.gen;

import sf.core.DBField;
import sf.core.DBObject;
import sf.database.dbinfo.ColumnInfo;
import sf.database.dbinfo.DBMetaData;
import sf.database.dbinfo.ObjectType;
import sf.database.dbinfo.TableInfo;
import sf.database.jdbc.type.JavaType;
import sf.database.util.DBUtils;
import sf.dsl.example.DBOperator;
import sf.tools.StringUtils;
import sf.tools.SystemUtils;

import java.sql.Connection;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * 代码生成类
 */
public class PojoGen {
    Connection conn;
    String pkg;
    String srcPath;
    GenConfig config;
    public static StringBuilder srcHead = new StringBuilder();
    public static String defaultPkg = "com.test";
    public static String CR = SystemUtils.lineSeparator;
    public static String template = null;

    static {
        srcHead.append("import java.math.*;").append(CR);
        srcHead.append("import java.util.Date;").append(CR);
        srcHead.append("import java.sql.Timestamp;").append(CR);
        srcHead.append("import javax.persistence.*;").append(CR);
        srcHead.append("import sf.database.annotations.*;").append(CR);

        template = GenUtils.getTemplate("/template/gen/pojo.enjoy");
    }

    public PojoGen(Connection conn, String pkg, String srcPath, GenConfig config) {
        this.conn = conn;
        this.pkg = pkg;
        this.config = config;
        if (srcPath == null) {
            this.srcPath = GenUtils.getJavaSRCPath();
        } else {
            this.srcPath = srcPath;
        }
    }

    /**
     * 生成代码
     */
    public void gen() throws Exception {
        List<TableInfo> tableInfos = DBMetaData.getInstance().getDatabaseObject(conn, ObjectType.TABLE, DBUtils.getCatalog(conn), DBUtils.getSchema(conn), null, DBOperator.EQUALS);
        for (TableInfo table : tableInfos) {
            List<ColumnInfo> columnInfos = DBMetaData.getInstance().getColumns(conn, table.getName());
            table.setColumnInfos(columnInfos);
            gen(table);
        }
    }

    public void gen(TableInfo info) {

        String className = StringUtils.firstCharToUpperCase(StringUtils.toCamelCase(info.getName()));
        String ext = null;

        if (config != null && config.getBaseClass() != null) {
            ext = config.getBaseClass();
        }

        List<ColumnInfo> cols = info.getColumnInfos();
        List<Map> attrs = new ArrayList<Map>();
        StringBuilder fieldEnums = new StringBuilder();
        boolean f = false;
        for (ColumnInfo col : cols) {

            Map<Object, Object> attr = new LinkedHashMap<>();
            attr.put("comment", col.getRemarks());
            String attrName = StringUtils.toCamelCase(col.getColumnName());
            attr.put("name", attrName);

            fieldEnums.append(f ? "," : "").append(attrName);
            f = true;

            attr.put("methodName", getMethodName(attrName));
            attr.put("columnName", col.getColumnName());
            attr.put("nullable", col.isNullable());

            //设置java类型
            attr.put("type", col.getRemarks());
            String type = JavaType.getType(col.getSqlType(), col.getColumnSize(), col.getDecimalDigit());
            if (config.isPreferBigDecimal() && type.equals("Double")) {
                type = "BigDecimal";
            }
            if (config.isPreferDate() && type.equals("Timestamp")) {
                type = "Date";
            }
            attr.put("type", type);

            //是否为主键
            boolean pk = false;
            if (info.getPrimaryKey().contains(col.getColumnName())) {
                pk = true;
            }
            attr.put("pk", pk);

            //设置日期类型
            String temporal = null;
            if (col.getSqlType() == Types.DATE) {
                temporal = "@Temporal(TemporalType.DATE)";
            } else if (col.getSqlType() == Types.TIMESTAMP || col.getSqlType() == Types.TIMESTAMP_WITH_TIMEZONE) {
                temporal = "@Temporal(TemporalType.TIMESTAMP)";
            } else if (col.getSqlType() == Types.TIME || col.getSqlType() == Types.TIME_WITH_TIMEZONE) {
                temporal = "@Temporal(TemporalType.TIME)";
            }
            attr.put("temporal", temporal);

            //是否是lob对象
            boolean isLob = false;
            if (JavaType.isLob(col.getSqlType())) {
                isLob = true;
            }
            if ("String".equals(type) && col.getColumnSize() > 5000) {
                isLob = true;
            }
            attr.put("lob", isLob);

            //是否是大数据
            boolean bigNumber = false;
            if (col.getSqlType() == Types.NUMERIC || col.getSqlType() == Types.DECIMAL) {
                bigNumber = true;
            }
            attr.put("bigNumber", bigNumber);

            attr.put("desc", col);
            attrs.add(attr);
        }

        if (config.getPropertyOrder() == GenConfig.ORDER_BY_TYPE) {
            // 主键总是拍在前面，int类型也排在前面，剩下的按照字母顺序排
            Collections.sort(attrs, new Comparator<Map>() {

                @Override
                public int compare(Map o1, Map o2) {
                    ColumnInfo desc1 = (ColumnInfo) o1.get("desc");
                    ColumnInfo desc2 = (ColumnInfo) o2.get("desc");
                    int score1 = score(desc1);
                    int score2 = score(desc2);
                    if (score1 == score2) {
                        return desc1.getColumnName().compareTo(desc2.getColumnName());
                    } else {
                        return score2 - score1;
                    }
                }

                private int score(ColumnInfo desc) {
                    if (info.getPrimaryKey().contains(desc.getColumnName())) {
                        return 99;
                    } else if (JavaType.isInteger(desc.getSqlType())) {
                        return 9;
                    } else if (JavaType.isDateType(desc.getSqlType())) {
                        return -9;
                    } else {
                        return 0;
                    }
                }
            });
        }

        Map<String, Object> map = new HashMap<>();
        map.put("attrs", attrs);
        map.put("className", className);
        map.put("tableName", info.getName());
        map.put("fieldEnums", fieldEnums.toString());
        map.put("ext", ext);
        map.put("package", pkg);
        map.put("imports", srcHead.toString());
        map.put("comment", info.getRemarks());
        map.put("table", info);
        map.put("DBObjectClassPath", DBObject.class.getName());
        map.put("DBFieldClassPath", DBField.class.getName());
        map.put("now", new Date());
        String code = GenUtils.renderByEnjoy(map, template);
        if (config.isDisplay()) {
            System.out.println(code);
        } else {
            GenUtils.saveSourceFile(srcPath, pkg, className, code);
        }

        for (CodeGen codeGen : config.codeGens) {
            codeGen.genCode(srcPath, pkg, className, info, config, config.isDisplay());
        }
    }


    private String getMethodName(String name) {
        if (name.length() == 1) {
            return name.toUpperCase();
        }
        char ch1 = name.charAt(0);
        char ch2 = name.charAt(1);
        if (Character.isLowerCase(ch1) && Character.isUpperCase(ch2)) {
            //aUname---> getaUname();
            return name;
        } else if (Character.isUpperCase(ch1) && Character.isUpperCase(ch2)) {
            //ULR --> getURL();
            return name;
        } else {
            //general  name --> getName()
            char upper = Character.toUpperCase(ch1);
            return upper + name.substring(1);
        }
    }
}

